package com.coldlz.mmp.ssl;

import cn.hutool.core.io.FileUtil;
import com.coldlz.mmp.proxy.ThreadPoolsUtil;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProtocols;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLException;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.List;

public class CertificateContext {
    static final Logger log = LoggerFactory.getLogger(CertificateContext.class);
    static final String KEY_PAIR_ALGORITHM = "RSA";
    static final int KEY_SIZE = 4096;
    static final long _365_DAY = 365 * 24 * 3600 * 1000L;
    static final String sigAlgName = "SHA512withRSA";


    private final String domain;
    private final KeyPairStr userKeyPair = KeyPairFactory.get();
    private final X509Certificate userCert;
    private SslContext sslContext;

    public KeyPairStr getUserKeyPair() {
        return userKeyPair;
    }

    public X509Certificate getUserCert() {
        return userCert;
    }

    public static CertificateContext create(String domain, CASignerProvider caSignerProvider) {
        long startTime = System.currentTimeMillis();
        try {
            if (hasCertFile(domain)) {
                return loadCertificateContext(domain);
            }
            return new CertificateContext(domain, caSignerProvider);
        } finally {
            long endTime = System.currentTimeMillis();
            //log.info("创建 CertificateContext 耗时 {}ms {}", (endTime - startTime), domain);
        }
    }

    private void saveCert() {
        FileUtil.writeUtf8String(X509CertificateUtil.toFileStr(this.userCert), getCertFile(domain));
    }

    private static File getCertFile(String domain) {
        return Util.getCertFile(domain);
    }

    private static CertificateContext loadCertificateContext(String domain) {
        List<String> cert = FileUtil.readLines(getCertFile(domain), StandardCharsets.UTF_8);
        X509Certificate x509Certificate = Util.loadCert(cert);
        return new CertificateContext(domain, x509Certificate);
    }

    private static boolean hasCertFile(String domain) {
        return getCertFile(domain).exists();
    }

    private CertificateContext(String domain, X509Certificate userCert) {
        this.domain = domain;
        this.userCert = userCert;
    }

    private CertificateContext(String domain, CASignerProvider caSignerProvider) {
        // 判断缓存目录下是否存在
        this.domain = domain;
        try {
            PKCS10CertificationRequest csr = createCsr(userKeyPair, domain);
            X509v3CertificateBuilder certBuilder = createCertBuilder(csr, caSignerProvider, domain);
            this.userCert = caSignerProvider.signature(certBuilder);
            ThreadPoolsUtil.cachedThreadPool.execute(this::saveCert);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        log.info("初始化 CertificateContext 结束...{}", domain);
    }


    private X509v3CertificateBuilder createCertBuilder(PKCS10CertificationRequest csr
            , CASignerProvider caSignerProvider, String domain) throws CertIOException {
        // 根据 csr 请求证书 + CA private key 签发证书
        // 构建证书信息
        X500Name issuer = caSignerProvider.issuer();
        BigInteger serial = getSerial(domain);// 证书序列号
        Date notBefore = new Date();
        Date notAfter = new Date(notBefore.getTime() + _365_DAY);// 365 天
        X500Name subject = csr.getSubject();
        SubjectPublicKeyInfo publicKeyInfo = csr.getSubjectPublicKeyInfo();
        X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(issuer, serial, notBefore, notAfter, subject, publicKeyInfo);

        AuthorityKeyIdentifier authorityKeyIdentifier = caSignerProvider.authorityKeyIdentifier();
        certBuilder.addExtension(Extension.authorityKeyIdentifier, false, authorityKeyIdentifier);


        certBuilder.addExtension(Extension.basicConstraints, false, new BasicConstraints(false));
        certBuilder.addExtension(Extension.keyUsage, false, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.nonRepudiation | KeyUsage.dataEncipherment));
        certBuilder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_serverAuth}));

        certBuilder.addExtension(Extension.subjectAlternativeName, false, getGeneralNames(domain));
        certBuilder.addExtension(Extension.subjectKeyIdentifier, false, new BcX509ExtensionUtils().createSubjectKeyIdentifier(publicKeyInfo));

        return certBuilder;
    }

    private BigInteger getSerial(String domain) {
        int hashCode = domain.hashCode();
        return new BigInteger(String.valueOf(hashCode));
    }

    private GeneralNames getGeneralNames(String domain) {
        GeneralNames generalNames;
        if (domain.matches("^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")) {
            generalNames = new GeneralNames(new GeneralName[]{
                    new GeneralName(GeneralName.iPAddress, domain)
            });
        } else {
            generalNames = new GeneralNames(new GeneralName[]{
                    new GeneralName(GeneralName.dNSName, domain),
                    new GeneralName(GeneralName.dNSName, "*." + domain)
            });
        }
        return generalNames;
    }

    private X500Name createX500Name(String cn) {
        return new X500NameBuilder(X500Name.getDefaultStyle())
                .addRDN(BCStyle.C, "CN")
                .addRDN(BCStyle.ST, "Sichuan")
                .addRDN(BCStyle.L, "Chengdu")
                .addRDN(BCStyle.O, "zfsw")
                .addRDN(BCStyle.OU, "yfb")
                .addRDN(BCStyle.CN, cn)
                .addRDN(BCStyle.E, "378661553@qq.com").build();
    }

    private PKCS10CertificationRequest createCsr(KeyPairStr keyPairStr, String commonName) throws IOException, OperatorCreationException {
        // 生成 csr 请求证书
        X500Name subject = createX500Name(commonName);
        ASN1InputStream asn1InputStream = new ASN1InputStream(keyPairStr.getPublicKey());
        SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(asn1InputStream.readObject());
        asn1InputStream.close();
        PKCS10CertificationRequestBuilder csrBuilder = new PKCS10CertificationRequestBuilder(subject, publicKeyInfo);


        AlgorithmIdentifier csrSigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(sigAlgName);
        AlgorithmIdentifier csrDigAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(csrSigAlgId);
        AsymmetricKeyParameter keyParam = PrivateKeyFactory.createKey(keyPairStr.getPrivateKey());
        ContentSigner csrSigner = new BcRSAContentSignerBuilder(csrSigAlgId, csrDigAlgId).build(keyParam);
        return csrBuilder.build(csrSigner);
    }


    public String getUserCertStr() {
        return X509CertificateUtil.toFileStr(userCert);
    }

    public String getUserPrivateKeyStr() {
        return userKeyPair.getPrivateKeyToFileStr();
    }

    public SslContext getOrNewSslContext() throws SSLException {
        if (sslContext != null) {
            return sslContext;
        }
        PrivateKey privateKey = userKeyPair.getKeyPair().getPrivate();
        SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(privateKey, userCert);
        sslContextBuilder.protocols(SslProtocols.TLS_v1
                , SslProtocols.TLS_v1_1
                , SslProtocols.TLS_v1_2
                , SslProtocols.TLS_v1_3);
        sslContext = sslContextBuilder.build();
        return sslContext;
    }
}
